<?php defined('BASEPATH') OR exit('No direct script access allowed');
require_once APPPATH.'eloquent_orm/core/Eloquent_prototype.php';
require_once APPPATH.'eloquent_orm/core/Elo_reflection.php';
require_once APPPATH.'eloquent_orm/core/Collection.php';
require_once APPPATH.'eloquent_orm/core/Pivot.php';

/**
 * Eloquent类<p>
 * 外部继承此类开始使用Eloquent ORM
 * @author 张皓旻
 *
 */
class Eloquent extends Eloquent_prototype
{   
    /**
     * 外部以::形式访问时的类名
     * @var string
     */
    static protected $_class_name = '';
    
    /**
     * Collection对象
     * @var object
     */
    static protected $_collection = NULL;
    
    /**
     * 使用find方法时默认查找的字段
     * @var string
     */
    protected $find_key = 'id';

    /**
     * 存储与之存在关联关系的Eloquent对象
     * @var array
     */
    private $_relations = array();
    
    /**
     * 实现伪静态访问形式<p>
     * 将外部访问的类名与Collection类的实例化对应<br>
     * 确保单例的同时保证每个Eloquent对象有与之对应的Collection对象
     * @param string $method
     * @param array $params
     * @return mixed
     */
    public static function __callstatic($method, $params)
    {
        if (self::$_collection === null OR
            self::$_class_name !== get_called_class())
        {
            self::$_collection = new Collection(get_called_class());
            self::$_class_name = get_called_class();
        }
        
        return call_user_func_array(array(self::$_collection, $method), $params);
    }
    
    /**
     * 获取Eloquent模型的属性<p>
     * attributes中存在则拿取
     * 否则从关联模型中读取
     * @see Eloquent_interface::__get()
     */
    public function __get($key)
    {
        return isset($this->_attributes[$key])
            ? $this->_attributes[$key]
            : $this->_get_relation($key);
    }
    
    /**
     * 获取关联数据模型
     * @param string $key
     * @return void|mixed
     */
    private function _get_relation($key)
    {
        if (isset($this->_relations[$key]))
        {
            return $this->_relations[$key];
        }
        // 确保此方法只存在于用户定义的Elo模型中
        elseif (Elo_reflection::has_own_method(get_class($this), $key))
        {
            // 调用不同关联方法, 获取数据对象, 并存入relation中, 脚本期间持久化
            $this->_relations[$key] = call_user_func(array($this, $key));
            return $this->_relations[$key];
        }
        else
        {
            show_error('The relationship has not been established');
        }
        
        return;
    }
    
    /**
     * 一对一<p>
     * 返回一个Eloquent对象
     * @param string $table_name 关联表表名
     * @param string $foreign_key 外键(默认为本表名+_+主键的形式)
     * @param string $local_key 本地键(默认为id)
     */
    public function has_one($table_name, $foreign_key = '', $local_key = '')
    {
        return $this->has_many($table_name, $foreign_key, $local_key)->first();
    }
    
    /**
     * 一对多<p>
     * 返回一个Collection对象
     * @param string $table_name 关联表表名
     * @param string $foreign_key 外键(默认为本表名+_+主键的形式)
     * @param string $local_key 本地键(默认为id)
     */
    public function has_many($table_name, $foreign_key = '', $local_key = '')
    {
        load_elo_orm($table_name);
        $class_name = ucfirst($table_name);
        
        if (empty($foreign_key))
        {
            $foreign_key = $this->table.'_'.$this->primary_key;
        }
        
        if (empty($local_key))
        {
            $local_key = $this->primary_key;
        }
        
        return $class_name::where($foreign_key, $this->_get_attribute($local_key))->get();
    }
    
    /**
     * 一对一和一对多的反方法<p>
     * 返回一个Eloquent对象
     * @param string $table_name 关联表表名
     * @param string $foreign_key 外键(默认为本表名+_+主键的形式)
     * @param string $local_key 本地键(默认为id)
     */
    public function belongs_to($table_name, $foreign_key = '', $local_key = '')
    {
        return $this->has_one($table_name, $foreign_key, $local_key);
    }
    
    /**
     * 多对多<p>
     * 返回一个Collection对象
     * 其中聚合的每个Eloquent对象都会带有一个pivot属性, 对应为中间表的Eloquent对象
     * 中间表也可以像普通的Eloquent对象一样使用
     * @param string $table_name 对应表名
     * @param string $relation_table 中间表名, 默认为本表名+_+对应表名
     * @param string $foreign_key 本表在中间表中的外键, 默认为表名+_+主键
     * @param string $other_foreign_key 对应表在中间表里的外键, 默认为表名+_+主键(两张表关联均为主键)
     */
    public function belongs_to_many($table_name, $relation_table = '', $foreign_key = '', $other_foreign_key = '')
    {
        load_elo_orm($table_name);
        $class_name = ucfirst($table_name);
        
        if (empty($relation_table))
        {
            $relation_table = $this->table.'_'.$table_name;
        }
        
        if (empty($foreign_key))
        {
            $foreign_key = $this->table.'_'.$this->primary_key;
        }
        
        if (empty($other_foreign_key))
        {
            $other_foreign_key = $table_name.'_'.$this->primary_key;
        }
        
        $class_name::where($foreign_key, $this->_get_attribute($this->primary_key));
        $pivot_data = Elo_reflection::call_private_method(get_class(self::$_collection), self::$_collection, '_get_other_table', array($relation_table));
        
        if (empty($pivot_data))
        {
            return;
        }
        
        $values = array();
        
        foreach ($pivot_data as $p)
        {
            $values[] = $p[$other_foreign_key];
        }
        
        $relations = $class_name::where_in($this->primary_key, $values)->get();
        // 向聚合成Collection对象的每个Eloquent对象都加上一个pivot对象
        return $this->_set_pivot($relation_table, $other_foreign_key, $relations, $pivot_data);
    }
    
    /**
     * 向多对多关联数据对象加上对应的pivot对象
     * @param string $relation_table
     * @param string $relation_foreign_key
     * @param object $relations
     * @param array $pivot_data
     * @return object
     */
    private function _set_pivot($relation_table, $relation_foreign_key, $relations, $pivot_data)
    {
        $pivot = new Pivot($relation_table);
        $primary_key = $this->primary_key;
        
        foreach ($relations as $key => $relation)
        {
            foreach ($pivot_data as $k => $p)
            {
                if ($relation->_get_attribute($primary_key) === $p[$relation_foreign_key])
                {
                    $new_pivot = clone $pivot;
                    $new_pivot->_attributes = $p;
                    $relation->_relations['pivot'] = $new_pivot;
                }
            }
        }
        
        return $relations;
    }
}